﻿using System;
using System.Collections.Generic;
using System.Text;
using System.Threading;
using System.IO.Ports;
using Mirkuz.PowerManagement;

namespace Mirkuz.LocationServices
{
    public class NMEAGPSLocationProvider : LocationProviderBase, IDisposable
    {
        Thread _gpsthread;
        string _comport="COM4";
        int _baudrate = 4800;
        bool _end_thread = false;
        DateTime lastfixtime;

        public NMEAGPSLocationProvider(string ComPort, int BaudRate, int PollTime)
        {
            _gpsthread = new Thread(new ThreadStart(gps_thread));
            _gpsthread.Name = "nmea_gps_parser";
        }

        public override void Start()
        {
            base.Start();
            _end_thread = false;
            _gpsthread.Start();
        }

        public override void Stop()
        {
            base.Stop();
            _end_thread = true;
        }

        void gps_thread()
        {
            while (!_end_thread)
            {
                gps_waitfix();
                Thread.Sleep(1000);
                for (int i = 0; (i < _pollingtime) && !_end_thread; i++)
                    Thread.Sleep(1000);                
            }
        }

        void gps_waitfix()
        {
            IntPtr _gpspwrreq=IntPtr.Zero;
            SerialPort p=null;
            try
            {
                _gpspwrreq = PowerManager.SetRequirement("GPS0:", CEDEVICE_POWER_STATE.D0);
                p = new SerialPort(_comport, _baudrate);
                p.ReadTimeout = 10000;
                p.Open();

                while (!_end_thread)
                {
                    try
                    {
                        char c;
                        string s = "";

                        do
                        {
                            c = (char)p.ReadChar();
                            s = s + c.ToString();
                        } while (((int)c) != 10 && s.Length < 500);

                        //System.Diagnostics.Debug.Write(s);

                        NMEAParse(s);
                    }
                    catch { }
                }
                p.Close();                
            }
            catch (Exception e)
            {
                OnError(e);
            }
            finally
            {
                PowerManager.ReleaseRequirement(_gpspwrreq);
                if (p != null && p.IsOpen)
                    p.Close();
                p.Dispose();
            }

        }

        #region nmea_parser
        void NMEAParse(string line)
        {
            if (line[0] != '$')
                return;
            char []c=new char[1];
            c[0]=',';
            
            string []par=line.Split(c);

            switch (par[0])
            {
                case "$GPRMC":
                    GPRMCParse(par);
                    break;
                case "$GPGGA":
                    GPGGAParse(par);
                    break;
                case "$GPGSA":
                    GPGSAParse(par);
                    break;
            }
        }
        void GPGSAParse(string[] par)
        {
            float pdop = 0;
            if (par[15] != "")
            {
                pdop = float.Parse(par[15], System.Globalization.CultureInfo.InvariantCulture);

                if (CurrentLocation != null)
                {
                    CurrentLocation.Range = Location.PDOPToRange(pdop);
                    OnNewLocationAvailable(CurrentLocation);
                }
            }
        }

        void GPRMCParse(string[] par)
        {
            double latitude = 0;
            double longitude = 0;
            DateTime fixtime = DateTime.Now;
            string time = par[1];
            string fixq = par[6];

            if ( par[1] != "" && 
                 par[2].ToUpper() == "A" && 
                 par[3] != "" && 
                 par[4] != "" && 
                 par[5] != "" && 
                 par[6] != "")
            {                
                //minimo set di informazioni necessarie

                fixtime = new DateTime(DateTime.Now.Year, DateTime.Now.Month, DateTime.Now.Day,
                                                int.Parse(par[1].Substring(0, 2)),
                                                int.Parse(par[1].Substring(2, 2)),
                                                int.Parse(par[1].Substring(4, 2)), DateTimeKind.Utc);

                latitude = double.Parse(par[3].Substring(0, 2), System.Globalization.CultureInfo.InvariantCulture) 
                            + double.Parse(par[3].Substring(2), System.Globalization.CultureInfo.InvariantCulture) / 60;
                longitude = double.Parse(par[5].Substring(0, 3), System.Globalization.CultureInfo.InvariantCulture)
                            + double.Parse(par[5].Substring(3), System.Globalization.CultureInfo.InvariantCulture) / 60;

                if (par[4].ToUpper() == "S")
                    latitude = -latitude;
                if (par[6].ToUpper() == "O")
                    longitude = -longitude;

                if (CurrentLocation == null)
                    CurrentLocation = new Location(latitude, longitude, fixtime);
                else
                {
                    CurrentLocation.Latitude = latitude;
                    CurrentLocation.Longitude = longitude;
                    CurrentLocation.UTCTimeOfFix = fixtime;
                    CurrentLocation.UTCTimeOfFixValid = true;
                }

                if (par[7] != "")
                {
                    CurrentLocation.Speed = double.Parse(par[7], System.Globalization.CultureInfo.InvariantCulture);
                    CurrentLocation.SpeedValid = true;
                }
                else CurrentLocation.SpeedValid = false;

                if (par[8] != "")
                {
                    CurrentLocation.Heading = double.Parse(par[8], System.Globalization.CultureInfo.InvariantCulture);
                    CurrentLocation.HeadingValid = true;
                }
                else CurrentLocation.HeadingValid = false;

                OnNewLocationAvailable(CurrentLocation);
            }
        }

        void GPGGAParse(string[] par)
        {
            double latitude=0;
            double longitude=0;
            DateTime fixtime=DateTime.Now;
            string time = par[1];
            string fixq = par[6];

            if (par[1] != "" && 
                par[2] != "" &&
                par[3] != "" && 
                par[4] != "" &&
                par[5] != "" &&
                par[6] !="0")
            {
                //minimo set di info necessarie

                fixtime = new DateTime(DateTime.Now.Year, DateTime.Now.Month, DateTime.Now.Day,
                                                int.Parse(par[1].Substring(0, 2)),
                                                int.Parse(par[1].Substring(2, 2)),
                                                int.Parse(par[1].Substring(4, 2)), DateTimeKind.Utc);

                latitude = double.Parse(par[2].Substring(0, 2), System.Globalization.CultureInfo.InvariantCulture) 
                                + double.Parse(par[2].Substring(2), System.Globalization.CultureInfo.InvariantCulture) / 60;
                longitude = double.Parse(par[4].Substring(0, 3), System.Globalization.CultureInfo.InvariantCulture)
                                + double.Parse(par[4].Substring(3), System.Globalization.CultureInfo.InvariantCulture) / 60;

                if (par[3].ToUpper() == "S")
                    latitude = -latitude;
                if (par[5].ToUpper() == "O")
                    longitude = -longitude;

                if (CurrentLocation == null)
                    CurrentLocation = new Location(latitude, longitude, fixtime);
                else
                {
                    CurrentLocation.Latitude = latitude;
                    CurrentLocation.Longitude = longitude;
                    CurrentLocation.UTCTimeOfFix = fixtime;
                    CurrentLocation.UTCTimeOfFixValid = true;
                }

                if (par[9] != "")
                {
                    CurrentLocation.Altitude=(int) double.Parse(par[9],System.Globalization.CultureInfo.InvariantCulture);
                    CurrentLocation.AltitudeValid = true;
                }

                lastfixtime = fixtime;
                OnNewLocationAvailable(CurrentLocation);

            }
        }
        #endregion

        #region IDisposable Members

        public override void Dispose()
        {
            _end_thread = true;
            try { _gpsthread.Abort(); }
            catch { }

        }

        #endregion
    }

}
